package dk.jwood.android.graph;

import java.util.Iterator;
import java.util.LinkedList;
import java.util.ListIterator;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;

import org.joda.time.DateTime;
import org.joda.time.Duration;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.Paint.Style;
import android.graphics.Path;
import android.graphics.PointF;
import android.graphics.Rect;
import android.util.Log;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import dk.jwood.android.graph.DataProvider.DataProviderListener;
import dk.jwood.android.graph.DataProvider.ValueDescriptor;
import dk.jwood.android.graph.GraphDef.Marker;



public class GraphView extends SurfaceView implements SurfaceHolder.Callback, DataProviderListener {
	
//	private static final int MAX_SIZE = 100;

	private static final String TAG = "GraphView";

	private GraphThread canvasthread;
	
	private GraphDef viewDef;
	
//	private double pixelsPerMs = 5d / 1000d;
	
	private DataProvider dataProvider;
	
	private Float maxValue;
	private Float minValue;
	
	private final AtomicReference<Double> fps;
	
	
	private int drawCount = 0;
	
	private DateTime lastPaint = null;
	
	
	private Bitmap bitmap;
	
	/**
	 * List sorted with newest element at the beginning
	 */
//	private LinkedList<GraphPoint> graphPoints;
	
	
//    public GraphView(Context context, AttributeSet attrs) {
//		super(context, attrs); 
//		// TODO Auto-generated constructor stub
//	    getHolder().addCallback(this);
//	    canvasthread = new GraphThread(getHolder(), this);
//	    setFocusable(true);
//	    
//	    setLayoutParams(new LayoutParams(150, 150));
//	    
//	    this.viewDef = new GraphDef();
//	    
//	    this.viewDef.update(getWidth(), getHeight());
//	    
//	    
//	    
//	}

	 


	 public GraphView(Context context, DataProvider dataProvider) {
		   super(context);
		    getHolder().addCallback(this);
		    canvasthread = new GraphThread(getHolder(), this);
		    setFocusable(true);

//		    graphPoints = new LinkedList<GraphPoint>();
		    
		    this.dataProvider = dataProvider;
		    
		    this.dataProvider.addListener(this);
		    
		    this.viewDef = new GraphDef(Duration.standardSeconds(10));

		    this.fps = new AtomicReference<Double>(0d);
		    
	    }


	@Override
	public void onDraw(Canvas canvas) {
//		Log.d(TAG, "onDraw: gp="+this.dataProvider.getValuesCount()+", w="+getWidth()+", h="+getHeight() );
		Paint paint = new Paint();

		
		DateTime now = new DateTime();
		
		long startUpdate = System.nanoTime();
		
		viewDef.update(getWidth(), getHeight(), now, minValue, maxValue);

		long endUpdate = System.nanoTime();
		
		
		long startBackground = System.nanoTime();
		
		int width = getWidth();
		int height = getHeight();
		int border = viewDef.getBorder();
		
		paint.setAntiAlias(true);
		
//		if(bitmap == null) {
//			
//			bitmap = Bitmap.createBitmap(width, height, 
//					Bitmap.Config.ARGB_8888); 
//			
//			Canvas cv = new Canvas(bitmap);
//			
//			canvas.
//			
//			
//			
//			
//		}
//		
//		if(drawCount % 100 != 0 && lastPaint != null) {
//			
//			Log.d(TAG, "translating clip...");
//			
//			canvas.clipRect(border, border, width - border, height - border);
//			
//			float xDiff = viewDef.translateDuration(now.getMillis() / lastPaint.getMillis());
//			
//			canvas.translate(-xDiff, 0);
//			
//			canvas.restore();
//			
//			return;
//			
//		}
//		canvas
		
		paint.setColor(Color.BLACK);
//		paint.setStyle(Style.STROKE);
		canvas.drawRect(new Rect(0, 0, width, height), paint);

		paint.setColor(Color.GREEN);
		canvas.drawText(this.dataProvider.getId(), 5, getHeight(), paint);

		paint.setTextSize(10);
		
		paint.setColor(Color.WHITE);
		// y
		canvas.drawLine(border, border, border, height - border, paint);
		
		// x
		canvas.drawLine(border, height - border, width - border, height - border, paint);
		
		
		canvas.drawText(String.format("%1$.1f",fps.get()), width - border, height - border, paint);
		
		
		Iterator<DateTime> smallPoints = viewDef.getXAxisSmallPoints(now);
		while(smallPoints.hasNext()) {
			
			DateTime next = smallPoints.next();
			float xPos = viewDef.translateTime(next.getMillis());
			
			if(xPos > width - border) continue;
			
			if(xPos < border) break;
			
			paint.setColor(Color.DKGRAY);
			
			canvas.drawLine(xPos, border, xPos, height - border, paint);
//			paint.setTextAlign(Align.CENTER);
//			if (i==horlabels.length-1)
//				paint.setTextAlign(Align.RIGHT);
//			if (i==0)
//				paint.setTextAlign(Align.LEFT);
			paint.setColor(Color.WHITE);
			canvas.drawText(next.toString("mm:ss"), xPos, height - 4, paint);
			
			
			
			
		}
		
		Iterator<Marker> yPoints = viewDef.getYAxisMarkers();
		
		while(yPoints.hasNext()) {
			Marker m = yPoints.next();
			
			paint.setColor(Color.DKGRAY);
			canvas.drawLine(border, m.getPosition(), width - border, m.getPosition(), paint);

			paint.setColor(Color.WHITE);
			
			canvas.drawText(m.getText(), 5,  m.getPosition(), paint);
			
		}
		
		long endBackground = System.nanoTime();
		
		long startValues = System.nanoTime();
		
		if(dataProvider.isEmpty()) return;
		
//		synchronized (graphPoints) {
			
//			double pixelsPerYPos = height / (maxValue - minValue);
			
//			Log.d(TAG, "pixelsPerYPos"+pixelsPerYPos+" height="+height);
			
			paint.setColor(Color.GREEN);
			
//			GraphPoint lastPoint = null;
			Float lastXPos = null;
			float lastYPos[] = new float[dataProvider.getValuesCount()];
			
			ValueDescriptor[] vd = new ValueDescriptor[dataProvider.getValuesCount()];
			
			for(int i = 0; i < vd.length; i++) {
				vd[i] = dataProvider.getValueDescriptor(i);
			}
			
//			PointF lastPoint = null;

//			Path[] p = new Path[dataProvider.getValuesCount()];
//			for(int i = 0; i < dataProvider.getValuesCount(); i++) {
//				p[i] = new Path();
//			}
			
			Iterator<DiscreetValue> graphPointsIterator = dataProvider.iterator();
			while(graphPointsIterator.hasNext())
				{
				
				DiscreetValue gp = graphPointsIterator.next();
				
				float xPos = viewDef.translateTime(gp.getTimestamp());
//				gp.setxPos(viewDef.getPixelForTime(gp.getTimestamp()));

				
				if(xPos < border) {
//					graphPointsIterator.remove();
//					Log.d(TAG, "Remove Point: "+gp+" minVal="+minValue+" maxVal="+maxValue);
//					continue;
					break;
				}
				
				
				for(int i = 0; i < gp.getValues().length; i++) {
					float yPos = viewDef.translateValue(gp.getValues()[i]);
					//Log.d(TAG, "Painting Point: "+gp);

					paint.setColor(vd[i].getColor());
					
//					if(lastXPos == null) {
//						p[i].moveTo(xPos, yPos);
//					} else {						
//						p[i].lineTo(xPos, yPos);
//					}
					
//					
					if(lastXPos == null) {
						paint.setColor(vd[i].getColor());
						
						String label = vd[i].getLabel();
						
						if(label == null) {
							label = "??";
						}
						
//						Log.d(TAG, "Drawing label: "+label);
						
						canvas.drawText(label, width - border, yPos, paint);
						
					} else {
						canvas.drawLine(lastXPos, lastYPos[i], xPos, yPos, paint);
					}

//					canvas.drawCircle(xPos, yPos, 2, paint);

					lastYPos[i] = yPos;
					
				}
				
//				for(int i = 0; i < dataProvider.getValuesCount(); i++) {
//					paint.setColor(vd[i].getColor());
//					paint.setStyle(Style.STROKE);
//					canvas.drawPath(p[i], paint);
//				}
				
				lastXPos = xPos;
				
			}
			
			long endValues = System.nanoTime();

			
			Duration calc = new Duration(startUpdate * 1000, endUpdate * 1000);
			Duration back = new Duration(startBackground * 1000, endBackground * 1000);
			Duration val = new Duration(startValues * 1000, endValues * 1000);
			
			Duration total = new Duration(calc.getMillis()+back.getMillis()+val.getMillis());
	
			double calcP = ((double)calc.getMillis() / (double)total.getMillis());
			double backP = ((double)back.getMillis() / (double)total.getMillis());
			double valP = ((double)val.getMillis() / (double)total.getMillis());
			
//			Log.d(TAG, "calc="+calc.toString()+" back="+back.toString()+" val="+val.toString());
			
			
			Log.d(TAG, String.format("calc=%1$.2f back=%2$.2f val=%3$.2f TOTAL=%4$s", calcP, backP, valP, total.toString()));
			
			
			drawCount++;
			lastPaint = now;
		}
	
		
//		canvas.drawColor(Color.GREEN);
//		paint.setColor(Color.BLUE);
//		canvas.drawCircle(100, 100, 20, paint);
		
//	}
	 
	 
	 
	@Override
	public void surfaceChanged(SurfaceHolder holder, int format, int width, int height) {
		
		Log.d("surfaceChanged NEW", "w="+width+" h="+height);
		
		Log.d("surfaceCreated call", "w="+getWidth()+" h="+getHeight());
		
		
		
	}
	
	@Override
	public void surfaceCreated(SurfaceHolder holder) {
		// TODO Auto-generated method stub
	    canvasthread.setRunning(true);
	    canvasthread.start();
	    
	    Log.d("surfaceCreated", "w="+getWidth()+" h="+getHeight());
		
	    
		
	}
	@Override
	public void surfaceDestroyed(SurfaceHolder holder) {
		// TODO Auto-generated method stub
		boolean retry = true;
		canvasthread.setRunning(false);
		while (retry) {
			try {
				canvasthread.join();
				retry = false;
			} catch (InterruptedException e) {
				// we will try it again and again...
			}
		}

	}

	
	private class GraphThread extends Thread {
	    private SurfaceHolder _surfaceHolder;
	    private GraphView _panel;
	    private boolean _run = false;
	    
	    private final long[] lastRuns;
	    private int numRuns;
	    
	    
	    public GraphThread(SurfaceHolder surfaceHolder, GraphView panel) {
	        _surfaceHolder = surfaceHolder;
	        _panel = panel;
	        
	        lastRuns = new long[10];
	        numRuns = 0;
	    }

	    public void setRunning(boolean run) {
	        _run = run;
	    }

	    @Override
	    public void run() {
	        Canvas c;
	        while (_run) {
	            c = null;
	            try {
	            	
	            	long start = System.nanoTime();
	            	
	                c = _surfaceHolder.lockCanvas(null);
	                synchronized (_surfaceHolder) {
	                    _panel.onDraw(c);
	                }
	                
	                long end = System.nanoTime();
	                
	                lastRuns[numRuns % 10] = end - start;
	                
	                numRuns++;
	                
	                if(numRuns % 10 == 0) {
	                	
	                	long total = 0;
	                	for(long l : lastRuns) {
	                		total += l;
	                	}
	                	// mean per frame in nanoseconds
	                	long meanNS = total / 10;

	                	double meanSec = (double)meanNS / 1000000000.0;
	                	
	                	double fps1 = 1 / meanSec;
	                	
	                	fps.set(fps1);
	                	
	                	
	                }
	                
	            } catch(Exception e) {
	            
	            	Log.d(TAG, "error ", e);
	            
	            } finally {
	                // do this in a finally so that if an exception is thrown
	                // during the above, we don't leave the Surface in an
	                // inconsistent state
	                if (c != null) {
	                    _surfaceHolder.unlockCanvasAndPost(c);
	                }
	            }
	            Thread.yield();
	        }
	    }
	}


	@Override
	public void pointAdded(DiscreetValue dp) {
		
//		GraphPoint gp = new GraphPoint(dp);
		
//		synchronized (graphPoints) {			
//			if(this.graphPoints.isEmpty() || this.graphPoints.getFirst().getDataPoint().getTimestamp() < dp.getTimestamp()) {
//				this.graphPoints.addFirst(gp);
//				
				for(float val : dp.getValues()) {
					if(maxValue == null || maxValue < val) {
						maxValue = val;
					}

					if(minValue == null || minValue > val) {
						minValue = val;
					}
				}

//				while(graphPoints.size() > MAX_SIZE) {
//					this.graphPoints.removeLast();
//				}
//			}
			
//		}
		
	}
	
//	private class GraphPoint {
//		
//		private final DiscreetValue dataPoint;
//		private float xPos;
//		private final float[] yPos;
//		
//		public GraphPoint(DiscreetValue dataPoint) {
//			this.dataPoint = dataPoint;
//			yPos = new float[dataPoint.getValues().length];
//		}
//		public float getxPos() {
//			return xPos;
//		}
//
//		public void setxPos(float xPos) {
//			this.xPos = xPos;
//		}
//
//		public float[] getyPos() {
//			return yPos;
//		}
//		
//		public float getyPos(int index) {
//			return yPos[index];
//		}
//
////		public void setyPos(float[] yPos) {
////			this.yPos = yPos;
////		}
//		
//		public void setyPos(int index, float yPos) {
//			this.yPos[index] = yPos;
//		}
//
//		public DiscreetValue getDataPoint() {
//			return dataPoint;
//		}
//		@Override
//		public String toString() {
//			return String.format("TS=%1$s(%3$s), V=%2$s(%4$s)", getDataPoint().getTimestamp(), getDataPoint().getValues(), getxPos(), getyPos());
//		}
//		
//		
//		
//	}
	
	

}   